Source code for qredtea.symmetries.symmetrygroups

# This code is part of qredtea.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
Symmetry groups defined the Abelian symmetries and combined Abelian symmetry groups.
"""

import abc
from dataclasses import dataclass, field

from qredtea.tooling import QRedTeaAbelianSymError

from .ibarrays import iarray, imod, izeros

__all__ = [
    "_AbstractAbelianSymGroup",
    "AbelianSymZN",
    "AbelianSymU1",
    "AbelianSymCombinedGroup",
]


class _AbstractAbelianSymGroup(abc.ABC):
    """
    Abstract Abelian symmetry group from which actual Abelian symmetries
    can be derived.
    """

    # def __ne__(self, other):
    #    """Comparing if instance of class is different from another symmetry."""
    #    return not (self == other)

    @abc.abstractmethod
    def __str__(self):
        """String representation of symmetry."""

    @abc.abstractmethod
    def __repr__(self):
        """String representation of symmetry."""

    # @abc.abstractmethod
    # def __eq__(self, other):
    #    """Comparing if instance of class is equal to another symmetry."""

    @abc.abstractmethod
    def __contains__(self, irrep_label):
        """Check if an irrep is part of this symmetry."""

    @property
    @abc.abstractmethod
    def identical_irrep(self):
        """Identical irrep which does not change charge / sector."""

    @abc.abstractmethod
    def invert_irrep(self, irrep):
        """Invert the irrep passed as argument according to the symmetry's rule."""

    @abc.abstractmethod
    def couple_irrep(self, irrep1, irrep2):
        """Couple the two irreps passed as argument according to the symmetry's rule."""

    @abc.abstractmethod
    def decouple_irrep(self, irrep1, irrep2):
        """Decouple the two irreps passed as argument according to the symmetry's rule."""

    @abc.abstractmethod
    def couple_multiple(self, irrep_vec, invert_irreps=False):
        """Deprecated."""


[docs] @dataclass(frozen=True) class AbelianSymZN(_AbstractAbelianSymGroup): """ Abelian symmetry of Zn group. **Arguments** order : int Order of the Zn symmetry, i.e., possible irreps range from 0 to (order - 1). """ order: int sym: str = field(default="Zn") # def __init__(self, order): # self.order = order def __post_init__(self): if self.sym != "Zn": raise QRedTeaAbelianSymError("U1 symmetry needs symmetry string to be U1.") def __str__(self): """String representation of Zn symmetry.""" return "Z%d" % (self.order) def __repr__(self): """String representation of Zn symmetry.""" return "Z%d" % (self.order) # def __eq__(self, other): # """Comparing if instance of class is equal to another symmetry.""" # if not isinstance(other, AbelianSymZN): # return False # return self.order == other.order def __contains__(self, irrep_label): """Check if an irrep is part of this symmetry.""" return 0 <= irrep_label < self.order @property def identical_irrep(self): """Identical irrep which does not change charge / sector.""" return 0
[docs] def invert_irrep(self, irrep): """Invert the irrep passed as argument according to the symmetry's rule.""" return imod(-irrep, self.order)
[docs] def couple_irrep(self, irrep1, irrep2): """Couple the two irreps passed as argument according to the symmetry's rule.""" return imod(irrep1 + irrep2, self.order)
[docs] def decouple_irrep(self, irrep1, irrep2): """Decouple the two irreps passed as argument according to the symmetry's rule.""" return imod(irrep1 - irrep2, self.order)
[docs] def couple_multiple(self, irrep_vec, invert_irreps=False): """Deprecated.""" if invert_irreps: irrep_vec *= -1 irrep = irrep_vec[0] for elem in irrep_vec[1:]: irrep += elem if invert_irreps: irrep_vec *= -1 # return imod(irrep, self.order) raise QRedTeaAbelianSymError("Untested and unused so far.")
[docs] @dataclass(frozen=True) class AbelianSymU1(_AbstractAbelianSymGroup): """ Abelian symmetry of U(1) group. **Arguments** No arguments. """ sym: str = field(default="U1") def __post_init__(self): if self.sym != "U1": raise QRedTeaAbelianSymError("U1 symmetry needs symmetry string to be U1.") def __str__(self): """String representation of U(1) symmetry.""" return "U1" def __repr__(self): """String representation of U(1) symmetry.""" return "U1" # def __eq__(self, other): # """Comparing if instance of class is equal to another symmetry.""" # return isinstance(other, AbelianSymU1) def __contains__(self, irrep): """Check if an irrep is part of this symmetry.""" return True @property def identical_irrep(self): """Identical irrep which does not change charge / sector.""" return 0
[docs] def invert_irrep(self, irrep): """Invert the irrep passed as argument according to the symmetry's rule.""" return -irrep
[docs] def couple_irrep(self, irrep1, irrep2): """Couple the two irreps passed as argument according to the symmetry's rule.""" return irrep1 + irrep2
[docs] def decouple_irrep(self, irrep1, irrep2): """Decouple the two irreps passed as argument according to the symmetry's rule.""" return irrep1 - irrep2
[docs] def couple_multiple(self, irrep_vec, invert_irreps=False): """Deprecated.""" if invert_irreps: irrep_vec *= -1 irrep = irrep_vec[0] for elem in irrep_vec[1:]: irrep += elem if invert_irreps: irrep_vec *= -1 # return irrep raise QRedTeaAbelianSymError("Untested and unused so far.")
[docs] class AbelianSymCombinedGroup(tuple): """ List of one or multiple Abelian symmetries used within a simulation. """ def __new__(cls, *args): if len(args) == 1 and hasattr(args[0], "__len__"): args = args[0] for elem in args: if not isinstance(elem, _AbstractAbelianSymGroup): raise TypeError( f"Entries must be AbelianSymGroup., but got {type(elem)}." ) return tuple.__new__(AbelianSymCombinedGroup, tuple(args)) def __contains__(self, irrep): """Check if an irrep is part of this combined symmetry.""" for ii, elem in enumerate(self): if irrep[ii] not in elem: return False return True @property def identical_irrep(self): """Identical irrep which does not change charge / sector.""" return iarray([self[ii].identical_irrep for ii in range(len(self))])
[docs] def invert_irrep(self, irrep): """ Invert the irrep passed as argument according to the symmetry's rule. **Arguments** irrep : integer array either single irrep (rank-1) or multiple (rank-2) """ if irrep.ndim == 1: return self._invert_single_irrep(irrep) return self._invert_multiple_irreps(irrep)
def _invert_single_irrep(self, irrep): """Invert single irrep passed as argument according to the symmetry's rule.""" nn = len(self) inv_irrep = izeros(nn) for ii, sym in enumerate(self): inv_irrep[ii] = sym.invert_irrep(irrep[ii]) return inv_irrep def _invert_multiple_irreps(self, irreps): """Invert irreps passed as argument row-by-row according to the symmetry's rule.""" inv_irrep = izeros(irreps.shape) for ii, sym in enumerate(self): inv_irrep[:, ii] = sym.invert_irrep(irreps[:, ii]) return inv_irrep
[docs] def couple_irreps(self, irrep1, irrep2): """ Couple the irreps passed as argument according to the symmetry's rule. **Arguments** irrep1 : integer array either single irrep (rank-1) or multiple (rank-2) irrep2 : integer array either single irrep (rank-1) or multiple (rank-2) """ if (irrep1.ndim == 1) and (irrep2.ndim == 1): return self._couple_irrep_irrep(irrep1, irrep2) if (irrep1.ndim == 2) and (irrep2.ndim == 1): return self._couple_irreps_irrep(irrep1, irrep2) if (irrep1.ndim == 1) and (irrep2.ndim == 2): return self._couple_irrep_irreps(irrep1, irrep2) if (irrep1.ndim == 2) and (irrep2.ndim == 2): if irrep1.shape[0] == 1: return self._couple_irrep_irreps(irrep1[0, :], irrep2) if irrep2.shape[0] == 1: return self._couple_irreps_irrep(irrep1, irrep2[0, :]) raise ValueError( f"Dimension irreps not available: {irrep1.shape}, {irrep2.shape}" )
def _couple_irrep_irrep(self, irrep1, irrep2): """Couple single irreps according to symmetry.""" nn = len(self) irrep = izeros(nn) for ii in range(nn): irrep[ii] = self[ii].couple_irrep(irrep1[ii], irrep2[ii]) return irrep def _couple_irreps_irrep(self, irreps1, irrep2): """Couple multiple irreps with single irreps according to symmetry.""" irrep = izeros(irreps1.shape) for ii, sym in enumerate(self): irrep[:, ii] = sym.couple_irrep(irreps1[:, ii], irrep2[ii]) return irrep def _couple_irrep_irreps(self, irrep1, irreps2): """Couple single irrep with multiple irreps according to symmetry.""" irrep = izeros(irreps2.shape) for ii, sym in enumerate(self): irrep[:, ii] = sym.couple_irrep(irrep1[ii], irreps2[:, ii]) return irrep
[docs] def product_couple_irreps(self, irreps1, irreps2, invert_1=False, invert_2=False): """ Build all combinations of irreps and couple them. **Arguments** irreps1 : integer array, rank-2 First set of irreps. irreps2 : integer array, rank-2 Second set of irreps. Fast-moving index. invert_1 : boolean, optional Flag if irreps1 should be inverted. Default to `False` invert_2 : boolean, optional Flag if irreps2 should be inverted. Default to `False` **Returns** irreps : integer arrray, rank-2 **Details** Method is the equivalent of itertools.product for two irreps. """ n1 = irreps1.shape[0] n2 = irreps2.shape[0] if invert_1: irreps_a = self.invert_irrep(irreps1) else: irreps_a = irreps1 if invert_2: irreps_b = self.invert_irrep(irreps2) else: irreps_b = irreps2 i1 = 0 i2 = n1 irreps = izeros((n1 * n2, irreps1.shape[1])) for ii in range(n2): irreps[i1:i2, :] = self.couple_irreps(irreps_a, irreps_b[ii, :]) i1 += n1 i2 += n1 return irreps
[docs] def decouple_irreps(self, irrep1, irrep2): """ Decouple the irreps passed as argument according to the symmetry's rule. **Arguments** irrep1 : integer array either single irrep (rank-1) or multiple (rank-2) irrep2 : integer array either single irrep (rank-1) or multiple (rank-2) """ if (irrep1.ndim == 1) and (irrep2.ndim == 1): return self._decouple_irrep(irrep1, irrep2) if (irrep1.ndim == 2) and (irrep2.ndim == 1): return self._decouple_irreps_irrep(irrep1, irrep2) if (irrep1.ndim == 1) and (irrep2.ndim == 2): return self._decouple_irrep_irreps(irrep1, irrep2) raise ValueError("Dimension irreps not available.")
def _decouple_irrep(self, irrep1, irrep2): """Decouple single irreps according to symmetry.""" irrep = izeros(len(self)) for ii, sym in enumerate(self): irrep[ii] = sym.decouple_irrep(irrep1[ii], irrep2[ii]) return irrep def _decouple_irreps_irrep(self, irreps1, irrep2): """Decouple multiple irreps with single irreps according to symmetry.""" irrep = izeros(irreps1.shape) for ii, sym in enumerate(self): irrep[:, ii] = sym.decouple_irrep(irreps1[:, ii], irrep2[ii]) return irrep def _decouple_irrep_irreps(self, irrep1, irreps2): """Decouple single irrep with multiple irreps according to symmetry.""" irrep = izeros(irreps2.shape) for ii, sym in enumerate(self): irrep[:, ii] = sym.decouple_irrep(irrep1[ii], irreps2[:, ii]) return irrep